package main

import (
	"flag"
	"os"
	"io/ioutil"
	"fmt"
	"os/exec"

	"bytes"
	"regexp"
	"strings"
	"path/filepath"
)

func main(){
	flagset := flag.NewFlagSet("pkg binary dist tool", flag.ExitOnError)
	var srcDir, dstDir string
	flagset.StringVar(&srcDir, "src", "", "original source code directory. multiple directories are separated by commas. e.g. /path/to/dir1,/path/to/dir2")
	flagset.StringVar(&dstDir, "dst", "", "destination stub source code directory. ")
	var verbose bool
	flagset.BoolVar(&verbose, "v", false, "show verbose information.")
	flagset.Parse(os.Args[1:])
	if srcDir == "" || dstDir == "" {
		fmt.Println(`"src" or "dst" is empty.` + "\r\n")
		os.Exit(1)
	}
	cmd := exec.Command("gofmt", "-h")
	cmdOut := bytes.NewBuffer(nil)

	cmd.Stderr = cmdOut
	cmd.Stdout = os.Stdout
	cmd.Run()
	cmdOutBtyes :=  cmdOut.Bytes()
	if string(cmdOutBtyes[:6]) != "usage:" {
		fmt.Println("gofmt test err.")
		os.Exit(1)
	}

	goPaths := strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator))
	if len(goPaths) < 1 {
		panic("GOPATH environment variable is not set.")
	}
	goPath, _ := filepath.Abs(goPaths[0])
	if len(goPaths) > 1{
		fmt.Printf("found GOPATH %+v", goPaths)
	}
	fmt.Printf("using \"%v\" as GOPATH\n", goPath)

	srcDirs := strings.Split(srcDir, "," )
	absDstDir, err := filepath.Abs(dstDir)
	if err != nil {
		panic(fmt.Sprintf("Invalid dst dir[%v]: %v", dstDir, err))
	}
	allFiles := []string{}
	for _, srcDir := range srcDirs {
		absSrcDir, err := filepath.Abs(srcDir)
		if strings.Index(absSrcDir, goPath) != 0 {
			panic(fmt.Sprintf("src [%v] dir must be inside GOPATH[%v]", srcDir, goPath))
		}
		if err != nil {
			panic(fmt.Sprintf("invalid src dir[%v]: %v", srcDir, err))
		}
		allFiles = append(allFiles, readChildDirFiles(absSrcDir)...)
	}

	for _, f := range allFiles {
		fd, err := os.Open(f)
		if err != nil {
			panic(err)
		}
		cmd := exec.Command("gofmt", fd.Name())
		cmdOut := bytes.NewBuffer(nil)
		cmd.Stderr = os.Stderr
		cmd.Stdout = cmdOut
		err = cmd.Run()
		if err != nil {
			panic(err)
		}
		codeStr := cmdOut.String()
		reg, err := regexp.Compile("[^\\n|\\r|\\r\\n]+(\\n|\\r|\\r\\n)")
		if err != nil {
			panic(err)
		}

		lines := reg.FindAll([]byte(codeStr), -1)
		packageName := ""
		imports := []string{}
		beginImport := false
		beginBlockComment := false

		for _, line := range lines {
			cLine := bytes.TrimSpace(line)
			if beginImport {
				if !beginBlockComment && string(cLine) == ")" {
					beginImport = false
					continue
				}
				if !beginBlockComment && len(cLine) > 2 && cLine[len(cLine) -1] == '"'{
					imports = append(imports, string(cLine))
					continue
				}else if !beginBlockComment && len(cLine) > 1 && string(cLine[0: 2]) == "/*" {
					beginBlockComment = true
					continue
				} else if len(cLine) > 1 && string(cLine[0: 2]) == "*/" {
					beginBlockComment = false
					continue
				}
			}
			if len(cLine) > 8 {
				if string(cLine[0:7]) == "package" {
					packageName = string(cLine[8:])
					continue
				}
			}

			if len(cLine) == 8 && string(cLine) == "import (" {
				beginImport = true
				continue
			} else if len(cLine) > 8 && string(cLine[0:8]) == "import \"" {
				imports = append(imports, string(cLine[7: len(cLine)]))
			}
		}

		relativePatth := strings.TrimPrefix(f, strings.TrimRight(goPath, string(os.PathSeparator)) + string(os.PathSeparator) + "src" + string(os.PathSeparator))
		dstFile := strings.TrimRight(absDstDir, string(os.PathSeparator)) + string(os.PathSeparator) + relativePatth
		dstDir = filepath.Dir(dstFile)
		err = os.MkdirAll(dstDir, 0755)
		if err != nil && !os.IsExist(err) {
			panic(fmt.Sprintf("dst directory[%v] create err: %v", dstDir, err))
		}
		content := fmt.Sprintf("//go:binary-only-package\n\npackage %v", packageName )
		if len(imports) > 0{
			content += "\n\nimport (\n"
			for _, s := range imports {
				content += "    " + s + "\n"
			}
			content += ")\n"
		}
		err = ioutil.WriteFile(dstFile, []byte(content), 0666)
		if err != nil{
			panic(fmt.Sprintf("failed to create stub  dst file[%v]: %v", dstFile, err))
		}
		if verbose {
			fmt.Printf("generated:  [%v] ==>>>> [%v] \n", f, dstFile)
		}
	}
	fmt.Println("\n\nall done. all files are created inside.\r\n" + absDstDir)
}

func readChildDirFiles(dir string)[]string{
	files := []string{}
	rawFiels, err := ioutil.ReadDir(dir)
	if err != nil {
		panic(err)
	}
	for _, fi := range rawFiels {
		if fi.IsDir(){
			files = append(files, readChildDirFiles(dir + string(os.PathSeparator) + fi.Name())...)
		} else {
			files = append(files, dir + string(os.PathSeparator) + fi.Name() )
		}
	}
	return files
}